<?php

/**
 * The Db class does connection into the database, queries and updates.
 * 
 * @author Cesar Augustus Silva <cesaraugustussilva@linuxmail.org>
 * @copyright Copyright (c) 2013, Cesar Augustus Silva
 * @license http://www.gnu.org/licenses/gpl.html GNU GPL v3.0
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <­http://www.gnu.org/licenses/>.
 */
class Db {

    /** @var mysqli The object which represents the connection with database */
    protected static $_link = null;
    
    /** @var string The host name or IP address of the database */
    protected static $_server = _DB_SERVER_;
    
    /** @var string The user name of the database */
    protected static $_username = _DB_USER_;
    
    /** @var string The user password of the database */
    protected static $_password = _DB_PASSWD_;
    
    /** @var string The name of the database */
    protected static $_database = _DB_NAME_;
    
    /** @var mysqli_stmt The object of statement */
    protected static $_stmt = null;
    
    /** @var int The auto generated id used in the last insertion */
    public static $insert_id = 0;
    
    /**
     * Opens connection to the database if it has no opening
     * 
     * @return void|boolean <b>TRUE</b> if already connected
     */
    public function connect() {
        if (self::$_link)
            return true;
        
        self::$_link = new mysqli(self::$_server, self::$_username, self::$_password, self::$_database);
        if (self::$_link->connect_errno || self::$_link->errno)
            die(self::displayError());
        
        self::$_link->set_charset('utf8');
    }
    
    /**
     * Closes connection to the database if it is open
     * 
     * @return void|boolean <b>TRUE</b> if already disconnected
     */
    public static function disconnect() {
        if (!self::$_link)
            return true;
        
        self::$_link->close();
        self::$_link = null;
    }
    
    /**
     * Display an error according to an error code
     * 
     * @return string The error message
     */
    protected static function displayError() {
        $errno = self::$_link->errno == null ? self::$_link->connect_errno : self::$_link->errno;
        $message = self::$_link->error == null ? self::$_link->connect_error : self::$_link->error;
        echo 'Erro '.$errno.': ';
        if (defined('_ERRMSG_'.$errno.'_')) {
            echo constant('_ERRMSG_'.$errno.'_');
            if (defined('_ERRPREG_'.$errno.'_')) {
                preg_match_all(constant('_ERRPREG_'.$errno.'_'), $message, $out);
                echo ' '.$out[1][0];
            }
        }
        else
            echo $message;
    }
    
    /**
     * Checks the type of variable
     * 
     * @param mixed $var The variable
     * @return string The specific character
     */
    protected static function variable_types($var) {
        if (is_numeric($var)) {
            if ((int) $var == $var)
                $type = 'i';
            else
                $type = 'd';
        }
        else {
            if (is_string($var) || empty($var))
                $type = 's';
            else
                $type = 'b';
        }
        return $type;
    }
    
    /**
     * Get data from the database with SQL queries
     * 
     * @staticvar int $cur_row The current row of the query result
     * @param string $query The SQL query
     * @return stdClass The object with the data and the status of the fetch
     */
    public function get($query) {
        self::connect();
        static $cur_row;
        if (!self::$_stmt) {
            self::$_stmt = self::$_link->prepare($query);
            if (func_num_args() > 1) {
                $types = '';
                $args = array();
                for ($i = 1; $i < func_num_args(); $i++) {
                    ${'arg'.$i} = func_get_arg($i);
                    $types .= self::variable_types(${'arg'.$i});
                    $args[] = '$arg'.$i;
                }
                eval('self::$_stmt->bind_param("'.$types.'",'.implode(',', $args).');');
            }
            self::$_stmt->execute();
            self::$_stmt->store_result();
            $cur_row = 0;
        }
        $rs_metadata = self::$_stmt->result_metadata();
        $vars = array();
        for ($i = 1; $i <= $rs_metadata->field_count; $i++) {
            ${'var'.$i} = null;
            $vars[] = '$var'.$i;
        }
        eval('self::$_stmt->bind_result('.implode(',',$vars).');');
        $rs = new stdClass;
        $rs->fetch = self::$_stmt->fetch();
        if ($rs->fetch) {
            $cur_row++;
            $n = 1;
            $rs->data = new stdClass;
            while ($field = $rs_metadata->fetch_field()) {
                $alias = $name = $field->name;
                $name = $field->table.'_'.$field->name;
                $rs->data->$name = ${'var'.$n++};
                if (!property_exists($rs->data, $alias))
                    $rs->data->$alias = &$rs->data->$name;
            }
        }
        elseif ($rs->fetch === false)
            die(self::displayError());
        if ($cur_row == self::$_stmt->num_rows) {
            self::$_stmt->close;
            self::$_stmt = null;
            $rs->fetch = null;
        }
        return $rs;
    }
    
    /**
     * Put data from the database of the table set
     * 
     * @param string $table The table name
     * @param array $data The data
     * @param array $where [optional]<br>The conditions for update
     * @return boolean <b>TRUE</b> on success or <b>FALSE</b> on failure
     */
    public function put($table, $data, $where = null) {
        self::connect();
        $types = '';
        $args = array();
        end($data);
        $last = each($data);
        if ($where == null)
            $query = 'INSERT INTO `'.$table.'` (';
        else
            $query = 'UPDATE `'.$table.'` SET ';
        foreach ($data as $key => $value) {
            $query .= '`'.$key.'`';
            if ($where != null)
                $query .= '=?';
            if ($key != $last['key'])
                $query .= ',';
            ${$key} = $value;
            $types .= self::variable_types(${$key});
            $args[] = '$'.$key;
        }
        if ($where == null) {
            $query .= ') values (';
            for ($i = 1; $i <= count($data); $i++) {
                $query .= '?';
                if ($i != count($data))
                    $query .= ',';
            }
            $query .= ')';
        }
        else {
            end($where);
            $last = each($where);
            $query .= ' WHERE ';
            foreach ($where as $key => $value) {
                $query .= '`'.$key.'`';
                if (is_numeric($value))
                    $query .= '=?';
                elseif (is_array($value)) {
                    $query .= ' IN (';
                    $n = 0;
                    foreach ($value as $val) {
                        $n++;
                        $query .= '?';
                        if ($n != count($value))
                            $query .= ',';
                        ${$key.$n} = $val;
                        $types .= self::variable_types(${$key.$n});
                        $args[] = '$'.$key.$n;
                    }
                    $query .= ')';
                }
                else
                    $query .= ' LIKE ?';
                if ($key != $last['key'])
                    $query .= ' AND ';
                if (!is_array($value)) {
                    ${$key.'_w'} = $value;
                    $types .= self::variable_types(${$key.'_w'});
                    $args[] = '$'.$key.'_w';
                }
            }
        }
        self::$_stmt = self::$_link->prepare($query);
        eval('self::$_stmt->bind_param("'.$types.'",'.implode(',', $args).');');
        $rs = self::$_stmt->execute();
        if ($where == null && $rs)
            self::$insert_id = self::$_stmt->insert_id;
        self::$_stmt->close;
        self::$_stmt = null;
        return $rs;
    }
    
    /**
     * Removes data from the database of the table set
     * 
     * @param string $table The table name
     * @param array $where The conditions for removal
     * @return boolean <b>TRUE</b> on success or <b>FALSE</b> on failure
     */
    public function rem($table, $where) {
        self::connect();
        $query = 'DELETE FROM `'.$table.'` WHERE ';
        $types = '';
        $args = array();
        end($where);
        $last = each($where);
        foreach ($where as $key => $value) {
            $query .= '`'.$key.'`';
            if (is_numeric($value))
                $query .= '=?';
            elseif (is_array($value)) {
                $query .= ' IN (';
                $n = 0;
                foreach ($value as $val) {
                    $n++;
                    $query .= '?';
                    if ($n != count($value))
                        $query .= ',';
                    ${$key.$n} = $val;
                    $types .= self::variable_types(${$key.$n});
                    $args[] = '$'.$key.$n;
                }
                $query .= ')';
            }
            else
                $query .= ' LIKE ?';
            if ($key != $last['key'])
                $query .= ' AND ';
            if (!is_array($value)) {
                ${$key.'_w'} = $value;
                $types .= self::variable_types(${$key.'_w'});
                $args[] = '$'.$key.'_w';
            }
        }
        self::$_stmt = self::$_link->prepare($query);
        eval('self::$_stmt->bind_param("'.$types.'",'.implode(',', $args).');');
        $rs = self::$_stmt->execute();
        self::$_stmt->close;
        self::$_stmt = null;
        return $rs;
    }

}